package com.ccp.dev.system.configuration;

import com.ccp.dev.core.basic.response.ResultData;
import com.ccp.dev.core.basic.response.ReturnCode;
import com.ccp.dev.core.basic.util.ContextUtil;
import com.ccp.dev.core.basic.util.UUIDUtils;
import com.ccp.dev.core.util.AppUtil;
import com.ccp.dev.core.util.RequestUtil;
import com.ccp.dev.system.model.SysErrorLog;
import com.ccp.dev.system.service.SysErrorLogService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;

/**
 * 通用错误处理器.
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class ControllerExceptionHandler extends AbstractErrorController {

    private Log log = LogFactory.getLog(ControllerExceptionHandler.class);

    @Resource
    private SysErrorLogService sysErrorLogService;

    private ErrorProperties errorProperties;

    public ControllerExceptionHandler(ErrorAttributes errorAttributes) {
        super(errorAttributes);
        errorProperties = AppUtil.getBean(ServerProperties.class).getError();
    }

    /**
     * 处理非页面级错误
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return  ResponseEntity
     */
    @RequestMapping
    @ResponseBody
    public ResponseEntity<?> handleErrors(HttpServletRequest request, HttpServletResponse response) {
        //拿到错误信息
        Map<String, Object> body = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.ALL));
        ResultData resultData = new ResultData();
        resultData.setCode(ReturnCode.FAIL.getCode());
        resultData.setMsg(body.get("message")!=null ? String.valueOf(body.get("message")): "");
        HttpStatus status = getStatus(request);
        handlerException(body);
        return new ResponseEntity<>(resultData, status);
    }

    /**
     * 处理页面级错误
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return ModelAndView
     */
    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView handleHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        handlerException(model);
        String trace = model.get("trace")!=null?(String)model.get("trace"):"";
        trace = trace.replaceAll("\\r\\n\\t","<br/>");
        model.put("trace",trace);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }

    /**
     * 判断是否需要嵌入堆栈信息
     * @param request  HttpServletRequest
     * @param produces MediaType
     * @return true 是  false 否
     */
    private boolean isIncludeStackTrace(HttpServletRequest request,MediaType produces) {
        ErrorProperties.IncludeStacktrace include = errorProperties.getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }

    @Override
    public String getErrorPath() {
        return errorProperties.getPath();
    }

    /**
     * 处理异常
     */
    private void handlerException(Map<String, Object> map){
        try{
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ip = RequestUtil.getIpAddr(request);
            String errorUrl = getErrorUrl(request);
            SysErrorLog sysErrorLog = new SysErrorLog();
            sysErrorLog.setId(UUIDUtils.getUUIDFor32());
            sysErrorLog.setAccount(ContextUtil.getCurrentUser().getAccount());
            sysErrorLog.setErrordate(new Date());
            sysErrorLog.setError(map.get("trace")!=null?String.valueOf(map.get("trace")):"");
            sysErrorLog.setIp(ip);
            sysErrorLog.setErrorurl(errorUrl);
            sysErrorLog.setTenantid(ContextUtil.getCurrentUserTenantId());
            sysErrorLog.setHashcode("");
            sysErrorLogService.add(sysErrorLog);
            log.error("发生异常："+map.toString());
        }catch (Exception e){
            e.printStackTrace();
            log.error("记录日志发生异常："+e);
        }
    }


    /**
     * 获取异常uri
     * @param request HttpServletRequest
     * @return errorUri
     */
    private String getErrorUrl(HttpServletRequest request){
        String errorUrl = RequestUtil.getErrorUrl(request);
        StringBuilder stringBuilder = new StringBuilder(errorUrl);
        Enumeration<?> e = request.getParameterNames();
        String para = "";
        String values = "";
        stringBuilder.append("?");
        while (e.hasMoreElements()) {
            para = (String) e.nextElement();
            values = request.getParameter(para);
            stringBuilder.append(para);
            stringBuilder.append("=");
            stringBuilder.append(values);
            stringBuilder.append("&");
        }
        return stringBuilder.substring(0, stringBuilder.length() - 1);
    }

    /*
     * 可以增加@ControllerAdvice  去识别某一个具体的异常如何处理  如下:
     * @param req
     * @param rsp
     * @param ex
     * @return
     * @throws Exception  500
     * @<code>
          @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
          @ExceptionHandler(Exception.class)
          public ModelAndView serverError(HttpServletRequest req, HttpServletResponse rsp, Exception ex) throws Exception {
              AntPathRequestMatcher matcher = new AntPathRequestMatcher("/api/**");
              if (matcher.matches(req)) {
                  log.error("!!! request uri:{} from {} server exception:{}", req.getRequestURI(), RequestUtil.getIpAddress(req), ex.getMessage());
                  ObjectMapper mapper = new ObjectMapper();
                  mapper.setSerializationInclusion(Include.NON_NULL);
                  String msg = mapper.writeValueAsString(BaseResponse.newFail(BaseResponse.STATUS_ERROR, "系统繁忙,请稍候重试"));
                  return handleJSONError(rsp, msg, HttpStatus.OK);
              } else {
                  throw ex;
              }
          }
           private ModelAndView handleJSONError(HttpServletResponse rsp, String errorMessage, HttpStatus status) throws IOException {
                rsp.setCharacterEncoding("UTF-8");
                rsp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                rsp.setStatus(status.value());
                PrintWriter writer = rsp.getWriter();
                writer.write(errorMessage);
                writer.flush();
                writer.close();
                return null;
             }
     *
     * </code>
     */
}




